package com.meshnetics.gb.type.impl.custom;

import com.meshnetics.gb.type.DataType;
import com.meshnetics.gb.type.DataValue;

import java.util.HashMap;

/**
 * Type for structure. Structure consists of fields. Each field has unique name and selected type for its value. Fields
 * can have null values (as java null). Fields can be accessed by index in order they are specified in constructor.
 *
 * @author AKovylin
 * @author Pavlov Dmitry
 */
public abstract class StructureDataType extends CustomDataType {
    private String[] fields;
    private HashMap fieldIndexMap;
    private DataType[] fieldTypes;

    protected StructureDataType(String name, String[] fields, DataType[] fieldTypes) {
        this(new String[]{name}, fields, fieldTypes);
    }

    protected StructureDataType(String[] aliases, String[] fields, DataType[] fieldTypes) {
        super(aliases);
        initFields(fields, fieldTypes);
    }

    /**
     * Performs initialization of structure with given field names and types. Using this constructor you can define if
     * it will be registered in types registry or not
     *
     * @param name of the type
     * @param register defines if the type will be registered or not. If it {@code true} then type will be registered in
     * types registry. If it {@code false} it won't.
     * @param fields fields names of structure
     * @param fieldTypes data types of fields
     */
    protected StructureDataType(String name, boolean register, String[] fields, DataType[] fieldTypes) {
        super(name, register);
        initFields(fields, fieldTypes);
    }

    /**
     * Performs initialization of structure fields
     *
     * @param fields array of field names
     * @param fieldTypes array of fields data types
     * @throws NullPointerException if some of the arrays are null.
     */
    private void initFields(String[] fields, DataType[] fieldTypes) {
        if (fields == null) {
            throw new NullPointerException("fields is null");
        }
        if (fieldTypes == null) {
            throw new NullPointerException("fieldTypes is null");
        }
        if (fields.length != fieldTypes.length) {
            throw new IllegalArgumentException("fields and fieldTypes lengths are not equal");
        }
        for (int i = 0; i < fields.length; i++) {
            if (fields[i] == null) {
                throw new NullPointerException("fields [" + i + "] is null");
            }
            if (fieldTypes[i] == null) {
                throw new NullPointerException("fieldTypes [" + i + "] is null");
            }
        }
        this.fields = fields;
        this.fieldTypes = fieldTypes;
        fieldIndexMap = new HashMap((int) (fields.length / 0.75f + 1), 0.75f);
        for (int i = 0; i < fields.length; i++) {
            if (fieldIndexMap.put(fields[i], new Integer(i)) != null) {
                throw new IllegalArgumentException(fields[i] + " field name is dublicated");
            }
        }
    }


    public String[] getFields() {
        return (String[]) fields.clone();
    }

    public DataType[] getFieldTypes() {
        return (DataType[]) fieldTypes.clone();
    }

    public int getFieldCount() {
        return fields.length;
    }

    public String getField(int index) {
        return fields[index];
    }

    public DataType getFieldType(int index) {
        return fieldTypes[index];
    }

    protected int getFieldIndex(String field) {
        return ((Integer) fieldIndexMap.get(field)).intValue();
    }

    protected String formatShortLogString(CustomDataValue value) {
        if (value == null) {
            throw new NullPointerException("value is null");
        }
        if (!(value instanceof StructureDataValue)) {
            throw new IllegalArgumentException("not a structure");
        }
        StructureDataValue sdv = (StructureDataValue) value;
        StringBuffer sb = new StringBuffer();
        sb.append(sdv.getType().getName()).append('(');
        for (int i = 0; i < fields.length; i++) {
            if (i > 0) {
                sb.append(',');
            }
            sb.append(fields[i]).append('=');
            DataValue val = sdv.getFieldValue(i);
            if (val == null) {
                sb.append("null");
            } else {
                sb.append(val.getType().getMandatoryFormatter().formatShortLogString(val));
            }
        }
        sb.append(')');
        return sb.toString();
    }

    protected String formatMaxLogString(CustomDataValue value) {
        if (value == null) {
            throw new NullPointerException("value is null");
        }
        if (!(value instanceof StructureDataValue)) {
            throw new IllegalArgumentException("not a structure");
        }
        StructureDataValue sdv = (StructureDataValue) value;
        StringBuffer sb = new StringBuffer();
        sb.append(sdv.getClass()).append('(').append(sdv.getType().getClass())
                .append('(').append(sdv.getType().getName()).append(')');
        for (int i = 0; i < fields.length; i++) {
            sb.append(',');
            sb.append(fields[i]).append('=');
            DataValue val = sdv.getFieldValue(i);
            if (val == null) {
                sb.append("null");
            } else {
                sb.append(val.getType().getMandatoryFormatter().formatMaxLogString(val));
            }
        }
        sb.append(')');
        return sb.toString();
    }
}
